<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Dagre Layout</title>
  </head>
  <style>
    .g6-tooltip {
      border: 1px solid #e2e2e2;
      border-radius: 4px;
      font-size: 12px;
      color: #545454;
      background-color: rgba(255, 255, 255, 0.9);
      padding: 10px 8px;
      box-shadow: rgb(174, 174, 174) 0px 0px 10px;
    }
  </style>
  <body>
    <div id="mountNode"></div>
    <script src="../build/dagre.js"></script>
    <script src="../build/g6.js"></script>
    <script>
      const data = {
        nodes: [
          {
            id: '1',
            label: '1',
          },
          {
            id: '2',
            label: '2',
          },
          {
            id: '3',
            label: '3',
          },
          {
            id: '4',
            label: '4',
          },
          {
            id: '5',
            label: '5',
          },
          {
            id: '6',
            label: '6',
          },
          {
            id: '7',
            label: '7',
          },
          {
            id: '8',
            label: '8',
            isLeaf: true,
          },
          {
            id: '9',
            label: '9',
            isLeaf: true,
          },
          {
            id: '10',
            label: '10',
            isLeaf: true,
          },
          {
            id: '11',
            label: '11',
            isLeaf: true,
          },
          {
            id: '12',
            label: '12',
            isLeaf: true,
          },
          {
            id: '13',
            label: '13',
            isLeaf: true,
          },
          {
            id: '14',
            label: '14',
            isLeaf: true,
          },
          {
            id: '15',
            label: '15',
            isLeaf: true,
          },
          {
            id: '16',
            label: '16',
            isLeaf: true,
          },
          {
            id: '17',
            label: '17',
            isLeaf: true,
          },
          {
            id: '18',
            label: '18',
            isLeaf: true,
          },
          {
            id: '19',
            label: '19',
            isLeaf: true,
          },
        ],
        edges: [
          {
            source: '1',
            target: '2',
          },
          {
            source: '1',
            target: '3',
          },
          {
            source: '3',
            target: '4',
          },
          {
            source: '2',
            target: '5',
          },
          {
            source: '5',
            target: '6',
          },
          {
            source: '5',
            target: '7',
          },
          {
            source: '7',
            target: '8',
          },
          {
            source: '6',
            target: '9',
          },
          {
            source: '6',
            target: '10',
          },
          {
            source: '6',
            target: '11',
          },
          {
            source: '6',
            target: '12',
          },
          {
            source: '6',
            target: '13',
          },
          {
            source: '6',
            target: '14',
          },
          {
            source: '4',
            target: '15',
          },
          {
            source: '4',
            target: '16',
          },
          {
            source: '4',
            target: '17',
          },
          {
            source: '4',
            target: '18',
          },
          {
            source: '4',
            target: '19',
          },
        ],
      };

      const graph = new G6.Graph({
        container: 'mountNode',
        width: 1000,
        height: 700,
        modes: {
          default: ['drag-node', 'drag-canvas'],
        },
        defaultNode: {
          shape: 'rect', // hexagon
          color: 'steelblue',
        },
        defaultEdge: {
          style: {
            stroke: '#aaa',
          },
        },
      });
      const nodes = data.nodes;
      const edges = data.edges;
      const nodeMap = new Map();
      const branchNodes = [];
      const branchEdges = [];
      const leafGroups = {};
      const branchNodesMap = new Map();
      const leafGroupOffset = 100;
      // separate the leaves and branch nodes
      nodes.forEach((node, i) => {
        nodeMap.set(node.id, node);
        if (node.isLeaf) {
          node.size = 30;
          node.shape = 'circle';
          edges.forEach(edge => {
            if (edge.source === node.id) {
              node.parent = edge.target;
            } else if (edge.target === node.id) {
              node.parent = edge.source;
            }
          });
          if (!leafGroups[node.parent]) leafGroups[node.parent] = [];
          leafGroups[node.parent].push(node);
        } else {
          branchNodes.push(node);
          branchNodesMap.set(node.id, node);
        }
      });
      edges.forEach(edge => {
        if (
          branchNodesMap.get(edge.source) !== undefined &&
          branchNodesMap.get(edge.target) !== undefined
        ) {
          branchEdges.push(edge);
        }
      });
      // layout the nodes with isLeaf === false by dagre
      const dagreLayout = new G6.Layout['dagre']({
        nodesep: 50,
        ranksep: 10,
      });
      dagreLayout.init({ nodes: branchNodes, edges: branchEdges });
      dagreLayout.execute();

      //layout the leaves with isLeaf === true by force
      const forceLayoutsMap = new Map();
      Object.keys(leafGroups).forEach(function(parentId) {
        const lg = leafGroups[parentId];
        const parent = branchNodesMap.get(parentId);
        const center = [parent.x, parent.y + leafGroupOffset];
        const forceLayout = new G6.Layout['force']({
          center,
          linkDistance: 0,
          nodeStrength: 30,
          collideStrength: 0.7,
          alphaDecay: 0.01,
          preventOverlap: true,
          tick: () => {
            graph.refreshPositions();
          },
        });
        forceLayout.init({ nodes: lg, edges: [] });
        forceLayout.execute();
        forceLayoutsMap.set(parentId, forceLayout);
      });

      graph.positionsAnimate();

      graph.data(data);
      graph.render();

      function refreshDragedNodePosition(e) {
        const model = e.item.get('model');
        model.fx = e.x;
        model.fy = e.y;
      }
      graph.on('node:dragstart', e => {
        const model = e.item.get('model');
        if (model.isLeaf) {
          const layoutMethod = forceLayoutsMap.get(model.parent);
          layoutMethod.ticking = false;
          layoutMethod.forceSimulation.stop();
          layoutMethod.execute();
          refreshDragedNodePosition(e);
        }
      });
      graph.on('node:drag', e => {
        const model = e.item.get('model');
        if (model.isLeaf) {
          refreshDragedNodePosition(e);
        }
      });
      graph.on('node:dragend', e => {
        const model = e.item.get('model');
        if (model.isLeaf) {
          e.item.get('model').fx = null;
          e.item.get('model').fy = null;
        }
      });
    </script>
  </body>
</html>
